/*
 * Copyright (c) 2023-2024 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.starter.login.configuration;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elsfs.cloud.api.security.service.SecurityUserService;
import org.elsfs.cloud.common.mybatis.properties.MybatisPlusProperties;
import org.elsfs.cloud.common.properties.ElsfsSecurityProperties;
import org.elsfs.cloud.common.security.authentication.JwtUserIdAuthenticationConverter;
import org.elsfs.cloud.common.security.config.CustomSecurityConfigurer;
import org.elsfs.cloud.common.security.configurer.AbstractServletSecurityCustomConfiguration;
import org.elsfs.cloud.common.security.handler.JsonAuthenticationEntryPoint;
import org.elsfs.cloud.common.security.handler.JsonAuthenticationHandler;
import org.elsfs.cloud.common.security.handler.LoginSuccessOrFailureHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsUtils;

/**
 * security 登录配置
 *
 * @author zeng
 */
@AutoConfiguration
@EnableMethodSecurity
@EnableWebSecurity
@EnableConfigurationProperties(ElsfsSecurityProperties.class)
@RequiredArgsConstructor
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Slf4j
public class ServletSecurityConfiguration extends AbstractServletSecurityCustomConfiguration {
  private final SecurityUserService securityUserService;
  private final NimbusJwtDecoder nimbusJwtDecoder;
  private final NimbusJwtEncoder nimbusJwtEncoder;
  private final ObjectProvider<ObjectMapper> objectMapperProvider;

  @Bean
  @ConditionalOnMissingBean
  LoginSuccessOrFailureHandler loginSuccessOrFailureHandler(
      ObjectMapper objectMapper,
      JwtEncoder jwtEncoder,
      MybatisPlusProperties mybatisPlusProperties) {
    String issuer = "http://loaclhost:8080";
    return new JsonAuthenticationHandler(objectMapper, jwtEncoder, issuer, mybatisPlusProperties);
  }

  /**
   * 将JwtAuthenticationToken 转换为 UsernamePasswordAuthenticationToken
   *
   * @return JwtUserIdAuthenticationConverter
   */
  @Bean
  JwtUserIdAuthenticationConverter jwtUserIdAuthenticationConverter() {
    return new JwtUserIdAuthenticationConverter(securityUserService);
  }

  @Bean
  @Order(2)
  SecurityFilterChain defaultSecurityFilterChain(
      HttpSecurity http, LoginSuccessOrFailureHandler loginSuccessOrFailureHandler)
      throws Exception {
    LOGGER.info("-----登录过滤链配置开始-----");
    // 配置响应头zz
    addHttpSecurityHeaders(http);
    http.securityContext(
        httpSecuritySecurityContextConfigurer ->
            httpSecuritySecurityContextConfigurer.requireExplicitSave(true));
    // 禁用csrf
    http.csrf(CsrfConfigurer::disable);
    // 启动cors跨域请求
    http.cors(Customizer.withDefaults());
    // 配置需要认证的请求
    //    addHttpSecurityAuthorizeHttpRequests(http);
    // 配置登录页面
    http.formLogin(FormLoginConfigurer::disable);
    // http.formLogin(login -> login.loginPage("/login"));
    // 退出登录
    autoConfigurationOauthLogout(http);
    // 配置认证 jwt
    var jsonAuthenticationEntryPoint =
        new JsonAuthenticationEntryPoint(objectMapperProvider.getIfUnique(ObjectMapper::new));
    //  oauth2资源服务器
    http.oauth2ResourceServer(
        o -> {
          // 配置解码器
          o.jwt(
              jwtConfigurer ->
                  jwtConfigurer
                      .decoder(nimbusJwtDecoder)
                      .jwtAuthenticationConverter(jwtUserIdAuthenticationConverter()));
          o.authenticationEntryPoint(jsonAuthenticationEntryPoint);
          o.accessDeniedHandler(loginSuccessOrFailureHandler);
        });
    autoConfigurationOauthLogin(http);
    http.exceptionHandling(
        exceptionHandling -> {
          exceptionHandling
              .authenticationEntryPoint(jsonAuthenticationEntryPoint)
              .accessDeniedHandler(loginSuccessOrFailureHandler);
        });
    ElsfsSecurityProperties properties = getElsfsSecurityProperties(http);
    http.authorizeHttpRequests(
        authorize -> {
          authorize.requestMatchers("/login").permitAll();
          authorize.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
          properties
              .getIgnoreHttpMethod()
              .forEach(
                  (requestMethod, strings) ->
                      strings.forEach(
                          s -> authorize.requestMatchers(requestMethod.name(), s).permitAll()));
          authorize.anyRequest().authenticated();
        });
    // 配置自定义的过滤器链
    http.with(
        new CustomSecurityConfigurer(),
        securityConfigurer -> {
          securityConfigurer.successHandler(loginSuccessOrFailureHandler);
          securityConfigurer.failureHandler(loginSuccessOrFailureHandler);
        });
    DefaultSecurityFilterChain chain = http.build();
    LOGGER.info("-----登录过滤链配置结束-----");
    return chain;
  }

  @Override
  public void afterPropertiesSet() throws Exception {}
}
